/*
 * Copyright 2001-2014 Aspose Pty Ltd. All Rights Reserved.
 *
 * This file is part of Aspose.Words. The source code in this file
 * is only intended as a supplement to the documentation, and is provided
 * "as is", without warranty of any kind, either expressed or implied.
 */
package com.aspose.words.examples.viewers_visualizers.document_explorer;

import com.aspose.words.*;

import javax.swing.*;
import javax.swing.tree.DefaultMutableTreeNode;
import javax.swing.tree.TreeNode;
import javax.swing.tree.TreePath;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Modifier;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Map;

/**
 * Base class used to provide GUI representation for document nodes.
 */
public class Item {
    private Node mNode;
    private DefaultMutableTreeNode mTreeNode;
    private ImageIcon mIcon;

    private static ArrayList<Field> mControlCharFields;
    private static Map<Integer, String> mNodeTypes;
    private static Map<Integer, String> mHeaderFooterTypes;
    private static Map<String, String> mItemSet;
    private static ArrayList mIconNames = new ArrayList();

    /**
     * Creates Item for the supplied document node.
     */
    public Item(Node node) {
        mNode = node;
    }

    /**
     * Returns the node in the document that this Item represents.
     */
    public Node getNode() {
        return mNode;
    }

    /**
     * The display name for this Item. Can be customized by overriding this method in inheriting classes.
     */
    public String getName() throws Exception {
        return getNodeTypeString(mNode);
    }

    /**
     * The text of the corresponding document node.
     */
    public String getText() throws Exception {
        String text = mNode.getText();

        // Most control characters are converted to human readable form.
        // E.g. [!PageBreak!], [!Cell!], etc.
        for (Field fieldInfo : mControlCharFields) {
            if (fieldInfo.getType() == char.class && Modifier.isStatic(fieldInfo.getModifiers())) {
                Character ch = fieldInfo.getChar(null);

                // Represent a paragraph break using the special formatting marker. This makes the text easier to read.
                if (fieldInfo.getName().equals("PARAGRAPH_BREAK_CHAR"))
                    text = text.replace(ch.toString(), "?" + "\n"); // JTextArea lines are separated using simple "\n" character and not using system independent new line character.
                else
                    text = text.replace(ch.toString(), java.text.MessageFormat.format("[!{0}!]", fieldInfo.getName().replace("_CHAR", "")));
            }
        }

        // All break chars should be supplemented with line feeds
        text = text.replace("BREAK!]", "BREAK!]\n");
        return text;
    }

    /**
     * Creates a TreeNode for this item to be displayed in the Document Explorer TreeView control.
     */
    public DefaultMutableTreeNode getTreeNode() throws Exception {
        if (mTreeNode == null) {
            mTreeNode = new DefaultMutableTreeNode(this);
            if (!mIconNames.contains(getIconName())) {
                mIconNames.add(getIconName());
            }

            if (mNode instanceof CompositeNode && ((CompositeNode) mNode).getChildNodes().getCount() > 0) {
                mTreeNode.add(new DefaultMutableTreeNode("#dummy"));
            }
        }
        return mTreeNode;
    }

    /**
     * Returns the icon to display in the Document Explorer TreeView control.
     */
    public ImageIcon getIcon() throws Exception {
        if (mIcon == null) {
            mIcon = loadIcon(getIconName());
            if (mIcon == null)
                mIcon = loadIcon("Node");
        }
        return mIcon;
    }

    /**
     * The icon for this node can be customized by overriding this property in the inheriting classes.
     * The name represents name of .ico file without extension located in the Icons folder of the project.
     */
    protected String getIconName() throws Exception {
        return getClass().getSimpleName().replace("Item", "");
    }

    /**
     * Provides lazy on-expand loading of underlying tree nodes.
     */
    public void onExpand() throws Exception {
        if ("#dummy".equals(getTreeNode().getFirstChild().toString())) {
            getTreeNode().removeAllChildren();
            Globals.mTreeModel.reload(getTreeNode());
            for (Object o : ((CompositeNode) mNode).getChildNodes()) {
                Node n = (Node) o;
                getTreeNode().add(Item.createItem(n).getTreeNode());
            }
        }
    }

    /**
     * Loads and returns an icon from the assembly resource stream.
     */
    private ImageIcon loadIcon(String iconName) {
        java.net.URL imgURL = MainForm.class.getResource("images/" + iconName + ".gif");
        if (imgURL != null)
            return new ImageIcon(imgURL);
        else
            return null;
    }

    /**
     * Removes this node from the document and the tree.
     */
    public void remove() throws Exception {
        if (this.isRemovable()) {
            mNode.remove();
            TreeNode parent = mTreeNode.getParent();
            mTreeNode.removeFromParent();
            Globals.mTreeModel.reload(parent);
            TreePath path = new TreePath(Globals.mRootNode);
            Globals.mTree.setSelectionPath(path);
        }
    }

    /**
     * Returns if this node can be removed from the document. Some nodes such as the last paragraph in the
     * document cannot be removed.
     */
    public boolean isRemovable() {
        return true;
    }

    /**
     * Static ctor.
     */
    static {
        // Populate a list of node types along with their class implementation.
        mItemSet = new HashMap<String, String>();
        for (Class itemClass : DocumentItems.class.getDeclaredClasses()) {
            try {
                String nodeTypeString = (String) itemClass.getField("NODE_TYPE_STRING").get(null);
                mItemSet.put(nodeTypeString, itemClass.getName());
            } catch (Exception e) {
                // IllegalAccessException, NoSuchFieldException or NoSuchMethodException - skip such exceptions if there are any.
            }
        }

        // Fill a list containing the information of each control char.
        mControlCharFields = new ArrayList<Field>();
        Field[] fields = ControlChar.class.getFields();
        for (Field fieldInfo : fields) {
            if (fieldInfo.getType() == char.class && Modifier.isStatic(fieldInfo.getModifiers())) {
                if (!fieldInfo.getName().equals("SPACE_CHAR"))
                    mControlCharFields.add(fieldInfo);
            }
        }

        // Map node type integer values to their equivalent string name.
        mNodeTypes = new HashMap<Integer, String>();
        Field[] nodeTypefields = NodeType.class.getFields();

        for (Field fieldInfo : nodeTypefields) {
            if (fieldInfo.getType() == int.class && Modifier.isStatic(fieldInfo.getModifiers())) {
                try {
                    int integerValue = fieldInfo.getInt(null);
                    mNodeTypes.put(integerValue, fieldInfo.getName());
                } catch (IllegalAccessException e) {
                    // Skip any invalid fields.
                }
            }
        }

        // Maps header/footer type integer values to string names.
        mHeaderFooterTypes = new HashMap<Integer, String>();
        fields = HeaderFooterType.class.getFields();

        for (Field fieldInfo : fields) {
            if (fieldInfo.getType() == int.class && Modifier.isStatic(fieldInfo.getModifiers())) {
                try {
                    int integerValue = fieldInfo.getInt(null);
                    mHeaderFooterTypes.put(integerValue, fieldInfo.getName());
                } catch (IllegalAccessException e) {
                    // Skip any invalid fields.
                }
            }
        }
    }

    /**
     * Item class factory implementation.
     */
    public static Item createItem(Node node) throws ClassNotFoundException, NoSuchMethodException,
            IllegalAccessException, InvocationTargetException,
            InstantiationException {
        String typeName = getNodeTypeString(node);
        if (mItemSet.containsKey(typeName))
            return (Item) Class.forName(mItemSet.get(typeName)).
                    getConstructor(DocumentItems.class, Node.class).
                    newInstance(null, node);
        else
            return new Item(node);
    }

    /**
     * Object.toString method used by Tree.
     */
    public String toString() {
        // Introduced non-checked RuntimeException on purpose to not change Object.toString() signature
        try {
            return getName();
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }

    /**
     * Convert numerical representation of the node type to string.
     */
    private static String getNodeTypeString(Node node) {
        int nodeType = node.getNodeType();
        if (mNodeTypes.containsKey(nodeType))
            return mNodeTypes.get(nodeType);
        else
            return "";
    }

    /**
     * Convert numerical representation of HeaderFooter integer type to string.
     */
    protected static String getHeaderFooterTypeAsString(HeaderFooter headerFooter) throws Exception {
        int headerFooterType = headerFooter.getHeaderFooterType();
        if (mHeaderFooterTypes.containsKey(headerFooterType))
            return mHeaderFooterTypes.get(headerFooterType);
        else
            return "";
    }
}